package search

import (
	"context"
	"encoding/json"
	"fmt"
	"gin_proj/common/utils"
	"gin_proj/config"
	"gin_proj/entity/dto"
	"gin_proj/entity/po"
	"strings"
	"time"

	"github.com/olivere/elastic/v7"
	"go.mongodb.org/mongo-driver/bson"
)

type SearchMapper struct{}

// 评分权重配置
const (
	ESScoreWeight      = 0.40    // ES默认分数占40%
	AIRatingWeight     = 0.30    // AI评分占30%
	UserRatingWeight   = 0.20    // 用户评分占20%
	ViewsWeight        = 0.10    // 阅读量占10%
	MaxViewsNormalized = 10000.0 // 用于归一化阅读量的基准值
)

func (m *SearchMapper) SearchArticle(ctx context.Context, searchDTO dto.ArticleSearchDTO) ([]po.ArticleES, int) {
	boolQuery := elastic.NewBoolQuery()

	// 关键词多字段搜索
	if searchDTO.Keyword != "" {
		multiMatch := elastic.NewMultiMatchQuery(searchDTO.Keyword, "title", "content", "tags").
			Type("best_fields").
			Operator("and")
		boolQuery.Must(multiMatch)
	}

	// 过滤已发布的文章 status=1
	boolQuery.Filter(elastic.NewTermQuery("status", 1))

	// 用户ID过滤（可选）
	if searchDTO.UserID != nil {
		boolQuery.Filter(elastic.NewTermQuery("userId", *searchDTO.UserID))
	}

	// 用户名过滤（可选）
	if searchDTO.Username != "" {
		boolQuery.Filter(elastic.NewWildcardQuery("username", "*"+searchDTO.Username+"*"))
	}

	// 分类名称完全匹配过滤（可选）
	if searchDTO.CategoryName != "" {
		boolQuery.Filter(elastic.NewTermQuery("category_name", searchDTO.CategoryName))
	}

	// 子分类名称完全匹配过滤（可选）
	if searchDTO.SubCategoryName != "" {
		boolQuery.Filter(elastic.NewTermQuery("sub_category_name", searchDTO.SubCategoryName))
	}

	// 发布时间范围过滤（可选）
	if searchDTO.StartDate != nil || searchDTO.EndDate != nil {
		rangeQuery := elastic.NewRangeQuery("create_at")
		layout := "2006-01-02 15:04:05"
		if searchDTO.StartDate != nil {
			if startTime, err := time.ParseInLocation(layout, *searchDTO.StartDate, time.Local); err == nil {
				rangeQuery.Gte(startTime.Format(layout))
			}
		}
		if searchDTO.EndDate != nil {
			if endTime, err := time.ParseInLocation(layout, *searchDTO.EndDate, time.Local); err == nil {
				rangeQuery.Lte(endTime.Format(layout))
			}
		}
		boolQuery.Filter(rangeQuery)
	}

	// 分页参数
	page := searchDTO.Page
	if page < 1 {
		page = 1
	}
	size := searchDTO.Size
	if size < 1 {
		size = 10
	}
	from := (page - 1) * size

	// 执行搜索（使用script_score在ES层面进行评分计算）
	esClient := config.ESClient

	// 构建综合评分脚本：(0.40 * _score) + (0.30 * ai_score/5) + (0.20 * user_score/5) + (0.10 * min(views/10000, 1))
	scoreScript := elastic.NewScript(`
		double score = params.esWeight * _score;
		double aiBoost = params.aiWeight * (doc['ai_score'].size() > 0 ? doc['ai_score'].value / 5.0 : 0);
		double userBoost = params.userWeight * (doc['user_score'].size() > 0 ? doc['user_score'].value / 5.0 : 0);
		double viewsBoost = params.viewsWeight * Math.min((double)doc['views'].value / params.maxViewsNormalized, 1.0);
		return score + aiBoost + userBoost + viewsBoost;
	`).
		Param("esWeight", ESScoreWeight).
		Param("aiWeight", AIRatingWeight).
		Param("userWeight", UserRatingWeight).
		Param("viewsWeight", ViewsWeight).
		Param("maxViewsNormalized", MaxViewsNormalized)

	// 使用script_score包装bool查询
	scriptScoreQuery := elastic.NewScriptScoreQuery(boolQuery, scoreScript)

	searchService := esClient.Search().
		Index("articles").
		Query(scriptScoreQuery).
		From(from).Size(size)

	// 如果有关键词，启用高亮
	if searchDTO.Keyword != "" {
		hl := elastic.NewHighlight().
			PreTags("<em>").PostTags("</em>")
		hl = hl.Fields(
			elastic.NewHighlighterField("title"),
			elastic.NewHighlighterField("content"),
			elastic.NewHighlighterField("tags"),
		).FragmentSize(150)
		searchService = searchService.Highlight(hl)
	}

	searchResult, err := searchService.Do(ctx)

	if err != nil {
		panic(err.Error())
	}

	// 解析结果
	var articles []po.ArticleES

	for _, hit := range searchResult.Hits.Hits {
		var article po.ArticleES
		if err := json.Unmarshal(hit.Source, &article); err == nil {
			// 如果有高亮，优先使用高亮内容覆盖字段（带 <em> 标签）
			if hit.Highlight != nil {
				if hs, ok := hit.Highlight["title"]; ok && len(hs) > 0 {
					article.Title = strings.Join(hs, " ")
				}
				if hs, ok := hit.Highlight["content"]; ok && len(hs) > 0 {
					article.Content = strings.Join(hs, " ")
				}
				if hs, ok := hit.Highlight["tags"]; ok && len(hs) > 0 {
					article.Tags = strings.Join(hs, " ")
				}
			}
			articles = append(articles, article)

			// 记录评分详情
			var score float64
			if hit.Score != nil {
				score = *hit.Score
			}
			utils.FileLogger.Info(fmt.Sprintf(
				"[搜索评分] 文章ID:%d | 标题:%.30s | AI评分:%.2f | 用户评分:%.2f | 阅读量:%d | 综合分数:%.4f",
				article.ID, article.Title, article.AIScore, article.UserScore, article.Views, score,
			))
		} else {
			// 处理解析错误
			panic(err.Error())
		}
	}

	return articles, int(searchResult.Hits.TotalHits.Value)
}

// GetSearchHistory 获取用户搜索历史记录（去重后最近10条）
func (m *SearchMapper) GetSearchHistory(ctx context.Context, userID int64) ([]string, error) {
	// 获取 MongoDB 集合
	collection := config.GetMongoDatabase().Collection("articlelogs")

	// 使用聚合管道实现去重后取前10条
	pipeline := []bson.M{
		// 1. 匹配条件：userId 和 action
		{
			"$match": bson.M{
				"userId": userID,
				"action": "search",
			},
		},
		// 2. 按创建时间倒序排序
		{
			"$sort": bson.M{
				"createdAt": -1,
			},
		},
		// 3. 添加 keyword 字段（从 content.Keyword 提取）
		{
			"$addFields": bson.M{
				"keyword": "$content.Keyword",
			},
		},
		// 4. 过滤掉空关键词
		{
			"$match": bson.M{
				"keyword": bson.M{
					"$exists": true,
					"$nin":    []interface{}{nil, ""},
				},
			},
		},
		// 5. 按关键词分组，保留最新的记录
		{
			"$group": bson.M{
				"_id":       "$keyword",
				"createdAt": bson.M{"$first": "$createdAt"},
			},
		},
		// 6. 再次按创建时间倒序排序
		{
			"$sort": bson.M{
				"createdAt": -1,
			},
		},
		// 7. 限制返回10条
		{
			"$limit": 10,
		},
		// 8. 只返回关键词字段
		{
			"$project": bson.M{
				"_id":       0,
				"keyword":   "$_id",
				"createdAt": 1,
			},
		},
	}

	// 执行聚合查询
	cursor, err := collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	// 解析结果
	var keywords []string
	for cursor.Next(ctx) {
		var result struct {
			Keyword   string    `bson:"keyword"`
			CreatedAt time.Time `bson:"createdAt"`
		}
		if err := cursor.Decode(&result); err != nil {
			continue
		}
		keywords = append(keywords, result.Keyword)
	}

	return keywords, nil
}
